use strict;
use AppConfig qw(:expand :argcount);

our $config;
our $article;
our $access;
our @quickref;
our %access_data_conf;

sub read_access($$) {
# Stage 1: Attempting to open spool file
	my $file 	= $_[0];				# Spool file
	my $expire	= $_[1];				# Expire time
	my %tempauth = %access_data_conf;			# Structure of access data
	my @tosave;						# Lines to save when expiring
	if ( -e $file ) {         				# Check whether spool_file exists
		if ( -r $file )	{				# Check whether spool_file is readable
			unless ( open( FILE, "$file" ) ) {	# If it's readable i try to read it, if it's impossible a fatal error will be bounced
				&log("crit", "Unable to open (read access) spool file: $file");
				return 1;
			}
			
			my $line;                       # Each line
			my $totlines = 0;		# Number of lines
			my $saved = 0;			# Number of lines saved (not expired)
			my $currip  = $article->clientip;    # which is the sender's ip address?			

			

			while( $line = <FILE>) {	# I read a line per time
				$totlines++;		# A new line added
				my @object = split( /\t/, $line );    # Split each line in elements
			        my $arrivaltime = $object[0];         # Time when message was sent
			        my $mid         = $object[1];         # Message-ID
        			my $exitcode    = $object[2];         # Whether a message was accepted (0) or rejected (everything non zero)
        			my $user        = $object[3];         # nnrpd user that has sent the message
        			my $clientip    = $object[4];         # client's ip address
        			my $clientdn    = $object[5];         # client's domain name (if domain name resolution is active else client's IP address)
        			my $crosspost   = $object[6];         # Number of crossposted groups
        			my $followup    = $object[7];         # Number of groups inside followup
        			my $headsize    = $object[8];         # Total size of all headers in bytes
        			my $bodysize    = $object[9];         # Size of body in bytes
        			my $totalsize   = $object[10];        # Total size (headers + body)
        			my $md5body     = $object[11];        # MD5 of body
				my $shortdn     = $object[12];	      # Shorted client's domain

				chop $shortdn;

				if ( $arrivaltime > ( time() - $expire)) {   # If an entry is too young to expire, it must be considered
					push( @tosave, $line );		     # it's saved in @tosave that will be stored on disk later 
					$saved++;			     # Number of 'saved' articles (that will be kept on disk)	

# Multipost counts how many articles with the same body were accepted by the server in the past. Note that rejected articles are not counted in order to avoid
# to force users to chenge the body of their articles when postfilter rejects a message due some error inside the headers
					$tempauth{"multipost"}++ if ( ($md5body eq $article->md5body()) and ($exitcode==0) ); 

					if ($clientip eq $currip) {	        # If so, current entry was sent by the same ip of current message
						$tempauth{"per_ip_total_articles"}++;	# Current IP has sent an article in the past	
						$tempauth{"per_ip_total_crosspost"} += $crosspost;  # Total number of groups inside Newsgroups
                                        	$tempauth{"per_ip_total_followups"} += $followup;   # Total number of groups inside Followup-To
                                                $tempauth{"per_ip_total_bodysize"} += $bodysize;    # Total number of bytes sent as body
                                                $tempauth{"per_ip_total_headsize"} += $headsize;    # Total number of bytes sent as headers
						$tempauth{"per_ip_total_size"} += $totalsize;	    # Total number of bytes sent by the current sender ip
						if ($exitcode == 0) {		# If $exitcode is set to zero, article was accepted
							$tempauth{"per_ip_accepted_articles"} += 1;
							$tempauth{"per_ip_accepted_headsize"} += $headsize; 							
							$tempauth{"per_ip_accepted_bodysize"} += $bodysize;
							$tempauth{"per_ip_accepted_totalsize"} += $totalsize;
							$tempauth{"per_ip_accepted_crosspost"} += $crosspost;
							$tempauth{"per_ip_accepted_followups"} += $followup;
						} else {			# if exit code is different than zero, article was rejected
							$tempauth{"per_ip_rejected_articles"}++; # counter is updated
							$tempauth{"per_ip_rejected_headsize"} += $headsize;
                                                	$tempauth{"per_ip_rejected_bodysize"} += $bodysize;
                                                	$tempauth{"per_ip_rejected_totalsize"} += $totalsize;
							$tempauth{"per_ip_rejected_crosspost"} += $crosspost;
                                                	$tempauth{"per_ip_rejected_followups"} += $followup;
						}
					}

                                        if ($user eq $article->user()) {         # If so, current entry was sent by the same user of current message
                                                $tempauth{"per_user_total_articles"}++;   # Current IP has sent an article in the past    
                                                $tempauth{"per_user_total_crosspost"} += $crosspost;  # Total number of groups inside Newsgroups
                                                $tempauth{"per_user_total_followups"} += $followup;   # Total number of groups inside Followup-To
                                                $tempauth{"per_user_total_bodysize"} += $bodysize;    # Total number of bytes sent as body
                                                $tempauth{"per_user_total_headsize"} += $headsize;    # Total number of bytes sent as headers                                    
				                $tempauth{"per_user_total_size"} += $totalsize;       # Total number of bytes sent by the current sender ip
                                                if ($exitcode == 0) {           # If $exitcode is set to zero, article was accepted
                                                        $tempauth{"per_user_accepted_articles"} += 1;
                                                        $tempauth{"per_user_accepted_headsize"} += $headsize;
                                                        $tempauth{"per_user_accepted_bodysize"} += $bodysize;
                                                        $tempauth{"per_user_accepted_totalsize"} += $totalsize;
                                                        $tempauth{"per_user_accepted_crosspost"} += $crosspost;
                                                        $tempauth{"per_user_accepted_followups"} += $followup;
                                                } else {                        # if exit code is different than zero, article was rejected
                                                        $tempauth{"per_user_rejected_articles"}++; # counter is updated
                                                        $tempauth{"per_user_rejected_headsize"} += $headsize;
                                                        $tempauth{"per_user_rejected_bodysize"} += $bodysize;
                                                        $tempauth{"per_user_rejected_totalsize"} += $totalsize;
                                                        $tempauth{"per_user_rejected_crosspost"} += $crosspost;
                                                        $tempauth{"per_user_rejected_followups"} += $followup;
                                                }
                                        }


                                        if ($shortdn eq $article->shortdn()) {         # If so, current entry was sent by the same domain of current message
                                                $tempauth{"per_domain_total_articles"}++;   # Current IP has sent an article in the past    
                                                $tempauth{"per_domain_total_crosspost"} += $crosspost;  # Total number of groups inside Newsgroups
                                                $tempauth{"per_domain_total_followups"} += $followup;   # Total number of groups inside Followup-To
                                                $tempauth{"per_domain_total_bodysize"} += $bodysize;    # Total number of bytes sent as body
                                                $tempauth{"per_domain_total_headsize"} += $headsize;    # Total number of bytes sent as headers
				                $tempauth{"per_domain_total_size"} += $totalsize;       # Total number of bytes sent by the current sender ip
                                                if ($exitcode == 0) {           # If $exitcode is set to zero, article was accepted
                                                        $tempauth{"per_domain_accepted_articles"} += 1;
                                                        $tempauth{"per_domain_accepted_headsize"} += $headsize;
                                                        $tempauth{"per_domain_accepted_bodysize"} += $bodysize;
                                                        $tempauth{"per_domain_accepted_totalsize"} += $totalsize;
                                                        $tempauth{"per_domain_accepted_crosspost"} += $crosspost;
                                                        $tempauth{"per_domain_accepted_followups"} += $followup;
                                                } else {                        # if exit code is different than zero, article was rejected
                                                        $tempauth{"per_domain_rejected_articles"}++; # counter is updated
                                                        $tempauth{"per_domain_rejected_headsize"} += $headsize;
                                                        $tempauth{"per_domain_rejected_bodysize"} += $bodysize;
                                                        $tempauth{"per_domain_rejected_totalsize"} += $totalsize;
                                                        $tempauth{"per_domain_rejected_crosspost"} += $crosspost;
                                                        $tempauth{"per_domain_rejected_followups"} += $followup;
                                                }
                                        }

					$tempauth{"global_total_articles"}++;   # Current IP has sent an article in the past    
                                        $tempauth{"global_total_crosspost"} += $crosspost;  # Total number of groups inside Newsgroups
                                        $tempauth{"global_total_followups"} += $followup;   # Total number of groups inside Followup-To
                                        $tempauth{"global_total_bodysize"} += $bodysize;    # Total number of bytes sent as body
                                        $tempauth{"global_total_headsize"} += $headsize;    # Total number of bytes sent as headers
                                        $tempauth{"global_total_size"} += $totalsize;       # Total number of bytes sent by the current sender ip
                                        if ($exitcode == 0) {           # If $exitcode is set to zero, article was accepted
                                        	$tempauth{"global_accepted_articles"} += 1;
                                                $tempauth{"global_accepted_headsize"} += $headsize;
                                                $tempauth{"global_accepted_bodysize"} += $bodysize;
                                                $tempauth{"global_accepted_totalsize"} += $totalsize;
                                                $tempauth{"global_accepted_crosspost"} += $crosspost;
                                                $tempauth{"global_accepted_followups"} += $followup;
                                        } else {                        # if exit code is different than zero, article was rejected
                                                $tempauth{"global_rejected_articles"}++; # counter is updated
                                                $tempauth{"global_rejected_headsize"} += $headsize;
                                                $tempauth{"global_rejected_bodysize"} += $bodysize;
                                                $tempauth{"global_rejected_totalsize"} += $totalsize;
                                                $tempauth{"global_rejected_crosspost"} += $crosspost;
   						$tempauth{"global_rejected_followups"} += $followup;
                                        }
				}
				
			}
			close FILE;

# Now it's the time to copy %tempauth inside $article
# Step 1: $article initialization

                foreach ( keys %tempauth ) {
                        $article->define( "$_", {
                                DEFAULT  => "0",
                                ARGCOUNT => ARGCOUNT_ONE,
                        } );
                        $article->set("$_", $tempauth{$_} );
                }


# Now some log data are printed through syslog

# Data about saved and expired articles
			my $expired = $totlines - $saved;
			&log("debug", "Spool: File $file, Total $totlines, expired $expired, kept $saved article on spool");
		
# Per IP data
			my $logline = "Spool: For $currip: accepted " . $tempauth{"per_ip_accepted_articles"}  . "/" . $tempauth{"per_ip_accepted_headsize"} . "/" . $tempauth{"per_ip_accepted_bodysize"} . "/" . $tempauth{"per_ip_accepted_totalsize"} . " " . $tempauth{"per_ip_accepted_crosspost"} . "/" . $tempauth{"per_ip_accepted_followups"};
			$logline .= ", rejected " . $tempauth{"per_ip_rejected_articles"} . "/" . $tempauth{"per_ip_rejected_headsize"} . "/" . $tempauth{"per_ip_rejected_bodysize"} . "/" . $tempauth{"per_ip_rejected_totalsize"} . " " . $tempauth{"per_ip_rejected_crosspost"} . "/" . $tempauth{"per_ip_rejected_followups"};
			&log("debug", $logline);

# Per Domain data

			$logline = "Spool: For " . $article->shortdn() . ": accepted " . $tempauth{"per_domain_accepted_articles"}  . "/" . $tempauth{"per_domain_accepted_headsize"} . "/" . $tempauth{"per_domain_accepted_bodysize"} . "/" . $tempauth{"per_domain_accepted_totalsize"} . " " . $tempauth{"per_domain_accepted_crosspost"} . "/" . $tempauth{"per_domain_accepted_followups"};
			$logline .= ", rejected " . $tempauth{"per_domain_rejected_articles"} . "/" . $tempauth{"per_domain_rejected_headsize"} . "/" . $tempauth{"per_domain_rejected_bodysize"} . "/" . $tempauth{"per_domain_rejected_totalsize"} . " " . $tempauth{"per_domain_rejected_crosspost"} . "/" . $tempauth{"per_domain_rejected_followups"};
			&log("debug", $logline);

# Per User data
			$logline = "Spool: For " . $article->user() . ": accepted " . $tempauth{"per_user_accepted_articles"}  . "/" . $tempauth{"per_user_accepted_headsize"} . "/" . $tempauth{"per_user_accepted_bodysize"} . "/" . $tempauth{"per_user_accepted_totalsize"} . " " . $tempauth{"per_user_accepted_crosspost"} . "/" . $tempauth{"per_user_accepted_followups"};
			$logline .= ", rejected " . $tempauth{"per_user_rejected_articles"} . "/" . $tempauth{"per_user_rejected_headsize"} . "/" . $tempauth{"per_user_rejected_bodysize"} . "/" . $tempauth{"per_user_rejected_totalsize"} . " " . $tempauth{"per_user_rejected_crosspost"} . "/" . $tempauth{"per_user_rejected_followups"};
			&log("debug", $logline);

# Global data
			$logline = "Spool: For global: accepted " . $tempauth{"global_accepted_articles"}  . "/" . $tempauth{"global_accepted_headsize"} . "/" . $tempauth{"global_accepted_bodysize"} . "/" . $tempauth{"global_accepted_totalsize"} . " " . $tempauth{"global_accepted_crosspost"} . "/" . $tempauth{"global_accepted_followups"};
			$logline .= ", rejected " . $tempauth{"global_rejected_articles"} . "/" . $tempauth{"global_rejected_headsize"} . "/" . $tempauth{"global_rejected_bodysize"} . "/" . $tempauth{"global_rejected_totalsize"} . " " . $tempauth{"global_rejected_crosspost"} . "/" . $tempauth{"global_rejected_followups"};
			&log("debug", $logline);

			$logline = "Spool: For " . $article->md5body() . ", found " . $tempauth{"multipost"} . " copies (multipost)";
			&log("debug", $logline )


		} else {				# if spool_file is not readable, it's useless to continue so it's the right time for a fatal error
			&log("crit", "Spool: Unable to read data from spool file: $file. Probably wrong permissions");
			return 1;
		}
	} else {					# If spool_file doesn't exist, no panic. This could be the first message (so spool_file was not yet created)
		&log("err", "Spool: spool file $file doesn't exist, this may be the first message");
		return 2;
	}
# Write data inside spool file
	unless ( open( FILE, ">$file" )) {
		&log("crit", "Spool: unable to write spool file $file, aborting");
		return 1;
	}
	
	foreach (@tosave) {
		print FILE $_;  # Save each entry in the spool file
	}
	
	return 0;
}

sub write_access($$) {
	my $file = $_[0];
	&log("debug", "Spool: appending data to spool file $file");
	unless ( open( FILE, ">>$file" )) {		# Try to open spool_file in append mode
		&log("crit", "Spool: unable to append data inside spool file $file");
		return 1;
	}

	my $arrivaltime = $article->arrivaltime(); 	# Time when message was sent
	my $mid 	= $article->mid();		# Message-ID
	my $exitcode 	= $_[1];			# Whether a message was accepted (0) or rejected (everything non zero)
	my $user	= $article->user();		# nnrpd user that has sent the message
	my $clientip    = $article->clientip();		# client's ip address
	my $clientdn	= $article->clientdn();		# client's domain name (if domain name resolution is active else client's IP address)
	my $crosspost	= $article->crosspost();	# Number of crossposted groups
	my $followup    = $article->followup();		# Number of groups inside followup
	my $headsize	= $article->headsize();		# Total size of all headers in bytes
	my $bodysize    = $article->bodysize();		# Size of body in bytes
	my $totalsize	= $article->totalsize();	# Total size (headers + body)
	my $md5body	= $article->md5body();		# MD5 of body
	my $shortdn	= $article->shortdn();		# Shorted client's domain

	print FILE "$arrivaltime\t$mid\t$exitcode\t$user\t$clientip\t$clientdn\t$crosspost\t$followup\t$headsize\t$bodysize\t$totalsize\t$md5body\t$shortdn\n";
	close FILE;

	&log("debug", "Spool: Writing into $file, $mid article with $exitcode exitcode");

	return 0;
}


sub check_access() {
	my $dropratio = $access->dropratio();
	foreach ( sort keys %access_data_conf ) {		    # Note: %access_data_conf must be sorted because drop_.+ settings must be scanned before the corresponding entries 
		my $setting = $_;
		my $limit   = $access->get("$setting");

		$setting =~ s/^drop\_// if ($setting =~ /^drop\_/); # Note: drop settings must be compared to the corresponding value so drop_per_ip_rejected_articles must
								    #       be compared with per_ip_rejected_articles value

		my $value   = $article->get("$setting");

		$setting = $_;					  # This is needed for logs

		my $drop    = sprintf("%d", ($limit * $dropratio));
		my $mid     = $article->mid();

		if ( $limit > 0 )  {   # if check is enabled
			if (	
					(($value >= $drop) and ($dropratio>0)) or		# dropratio exceeded
					(($setting =~ /^drop\_/) and ($value >= $limit))	# drop_ setting
			   ) {		# If the current article exceeds a drop limit and a drop ratio is defined 
                                &log("debug", "Access: $mid $setting $value $limit  $drop      DROP");
				my $tline = "Message $mid rejected and connection closed: $setting = $value, limit at $limit";
				$tline .= ", drop ratio at $dropratio, drop value at $drop, current value $value" if (($dropratio>0) and ($value>=$drop)); # logs looks nicer
                                &log("err", $tline);

                                my $exitcode;    
                                if ($setting =~ /multipost/i) {  # Multipost has got a different error string (so also return value)
                                        $exitcode = 1;
                                } else {
                                        $exitcode = 2;
                                }

                                &log( "err", "Message $mid rejected and connection closed, $quickref[$exitcode]");
				return ($exitcode*-1);

                        } elsif ( $value >= $limit ) { # if current articles exceeds a limit
				&log("debug", "Access: $mid $setting $value $limit  $drop      ERROR") if ($config->verbose == 1);

				my $exitcode;
				if ($setting =~ /multipost/i) {
					$exitcode = 1;
				} else {
					$exitcode = 2;
				}

				&log( "err", "Message $mid rejected, $quickref[$exitcode]");
				&log( "debug", "returning $exitcode");
				return $exitcode;
			}  else {
				&log("debug", "Access: $mid $setting $value $limit  $drop      PASS") if ($config->verbose == 1);
			}
		} else {
				&log("debug", "Access: $mid $setting disabled") if ($config->verbose == 1);
		}

	}
	return 0;
}

1;
